﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Microsoft.SharePoint;
using Microsoft.Office.RecordsManagement.RecordsRepository;


//Copies versions of documents from Source Library into Dest Library.  Appends the version number to the file name.

namespace VersionHistoryExtractor
{   
    public partial class FormMain : Form
    {
        private bool LatestColumnExists = false;
        public FormMain()
        {
            InitializeComponent();
        }

        private void btnExtract_Click(object sender, EventArgs e)
        {
            InitializeLog();
            string sOrigText = this.btnExtract.Text;

            try
            {
                this.btnExtract.Text = "Processing";
                WriteLog("Begin Processing: " + DateTime.Now.ToLongDateString() + " " + DateTime.Now.ToLongTimeString());
                this.btnExtract.Enabled = false;

                //Get SharePoint objects
                using (SPSite srcSite = new SPSite(this.tbSourceUrl.Text))
                {
                    using (SPWeb srcWeb = srcSite.OpenWeb(this.tbSourceUrl.Text))
                    {
                        SPDocumentLibrary srcDocLib = (SPDocumentLibrary)srcWeb.GetList(this.tbSourceUrl.Text);
                        //Validate tbVersionColumn value matches a field name
                        if (!string.IsNullOrEmpty(tbVersionColName.Text) && !srcDocLib.Fields.ContainsField(tbVersionColName.Text))
                        {
                            WriteLog("Invalid Version Column Name.  Aborting.");
                        }
                        else //Continue
                        {
                            WriteLog("Copying from source library: " + srcDocLib.Title);
                            using (SPSite destSite = new SPSite(this.tbDestUrl.Text))
                            {
                                using (SPWeb destWeb = srcSite.OpenWeb(this.tbDestUrl.Text))
                                {
                                    destWeb.AllowUnsafeUpdates = true;
                                    SPDocumentLibrary destDocLib = (SPDocumentLibrary)destWeb.GetList(this.tbDestUrl.Text);
                                    LatestColumnExists = destDocLib.Fields.ContainsField(tbLatestColumnName.Text);
                                    WriteLog("Copying to destination library: " + destDocLib.Title);
                                    ProcessItems(srcDocLib, destDocLib);
                                    destWeb.AllowUnsafeUpdates = false;
                                }
                            }
                        }
                    }
                }
            }
            finally
            {
                WriteLog("Processing complete: " + DateTime.Now.ToLongDateString() + " " + DateTime.Now.ToLongTimeString());
                FinalizeLog();
                this.btnExtract.Text = sOrigText;
                this.btnExtract.Enabled = true;
            }
        }

        private void ProcessItems(SPDocumentLibrary SrcLibrary, SPDocumentLibrary DestLibrary)
        {
            foreach (SPListItem srcListItem in SrcLibrary.Items)
            {
                //Copy versions from oldest to newest (to retain proper versioning if using a version column)
                SPFile srcFile = srcListItem.File;
                SPFileVersionCollection versions = srcFile.Versions;
                for (int i = 0; i < versions.Count; i++)
                {
                    SPFileVersion version = versions[i];
                    CopyVersion(DestLibrary, version);
                }

                //Copy current version
                CopyFile(DestLibrary, srcFile);
            }
            DestLibrary.Update();
        }

        private void CopyFile(SPDocumentLibrary DestLibrary, SPFile SrcFile)
        {
            string strFilename = NewFilename(SrcFile);
            CopyBinary(DestLibrary, strFilename, SrcFile.OpenBinary(), true, SrcFile.Properties);
        }

        private void CopyVersion(SPDocumentLibrary DestLibrary, SPFileVersion SrcVersion)
        {
            string strFilename = NewFilename(SrcVersion);
            CopyBinary(DestLibrary, strFilename, SrcVersion.OpenBinary(), false, SrcVersion.Properties);
        }

        private void CopyBinary(SPDocumentLibrary DestLibrary, string strFilename, byte[] FileBinary, bool isLatest, System.Collections.Hashtable PropertiesHash)
        {
            WriteLog("Saving File: " + strFilename, false);
            if (isLatest) { WriteLog(" - Latest", false); }
            try
            {
                SPSecurity.RunWithElevatedPrivileges(delegate()
                {
                    string destFileUrl = DestLibrary.RootFolder.ServerRelativeUrl + "/" + strFilename;
                    SPFile destFile = DestLibrary.RootFolder.ParentWeb.GetFile(destFileUrl);
                    if (destFile.Exists) //Bypass Locks, Running as System
                    {
                        bool wasRecord = false;
                        if (Records.IsRecord(destFile.Item))
                        {
                            Records.UndeclareItemAsRecord(destFile.Item);
                            WriteLog(" ... Undeclare Record", false);
                            wasRecord = true;
                        }
                            using (SPSite site = new SPSite(DestLibrary.ParentWeb.Url, destFile.Web.Site.SystemAccount.UserToken))
                            {
                                using (SPWeb web = site.OpenWeb())
                                {
                                    SPListItem elevatedItem = web.GetListItem(destFile.ServerRelativeUrl);
                                    Records.BypassLocks(elevatedItem, delegate(SPListItem bypassItem)
                                    {
                                        SaveFile(bypassItem.File.ParentFolder.DocumentLibrary, FileBinary, strFilename, bypassItem.File, isLatest, bypassItem.Properties, wasRecord);
                                    });
                                }
                            }
                    }
                    else
                    {
                        SaveFile(DestLibrary, FileBinary, strFilename, destFile, isLatest, PropertiesHash, false);
                    }
                });
            }
            catch (SPException ex)
            {
                if (this.cbOverwrite.Checked)
                {
                    WriteLog("");
                    WriteLog("**** Unexpected error.  File " + strFilename + " skipped.");
                    WriteLog("Additional Info: " + ex.ToString());
                }
                else
                {
                    WriteLog("");
                    WriteLog("**** Overwrite not enabled and file already exists.  File " + strFilename + " skipped.");
                    WriteLog("Additional Info: " + ex.Message);
                }
            }
        }

        private void SaveFile(SPDocumentLibrary DestLibrary, byte[] FileBinary, string strFilename, SPFile destFile, bool isLatest, System.Collections.Hashtable PropertiesHash, bool wasRecord)
        {
            if (destFile.Exists && !cbOverwrite.Checked)
            {
                WriteLog("File already exists.");
                return; //Do not save
            }

            if (destFile.Exists && cbOverwrite.Checked)
            {
                if (destFile.CheckOutType != SPFile.SPCheckOutType.None)
                {
                    if (!(destFile.MajorVersion == 1 && destFile.MinorVersion == 0))
                    {
                        destFile.UndoCheckOut();
                        WriteLog(" ... UndoCheckOut", false);
                    }
                    else
                    {
                        destFile.CheckIn("VHE: Overwrite checkin.", SPCheckinType.OverwriteCheckIn);
                        WriteLog(" ... Overwrite CheckIn", false);
                    }
                }

                if (destFile.Exists)
                {
                    WriteLog(" ... Checking out", false);
                    destFile.CheckOut();
                    destFile.Update();
                }
            }

            WriteLog(" ... Saving", false);
   
            //Attempt to save.  Trying 10 times with a .5 second pause between tries.
            bool wait = true;
            int iteration = 0;
            int maxIterations = 10;
            while (wait && iteration < maxIterations)
            {
                try
                {
                    destFile = DestLibrary.RootFolder.Files.Add(strFilename, FileBinary, PropertiesHash, cbOverwrite.Checked);
                    wait = false;
                }
                catch (Exception ex)
                {
                    if (iteration == 0) { WriteLog(" ... Waiting for Lock", false); }
                    iteration += 1;
                    WriteLog(".", false);
                    System.Threading.Thread.Sleep(500);
                    this.Refresh();
                    if (iteration == maxIterations) throw ex;
                }
            }
          
            //Set latest column
            if (this.LatestColumnExists)
            {
                WriteLog(" ... Setting Latest", false);
                destFile.Properties[tbLatestColumnName.Text] = Convert.ToInt32(isLatest);
                destFile.Update();
            }

            //Check In
            if (destFile.CheckOutType != SPFile.SPCheckOutType.None)
            {
                WriteLog(" ... Checking In", false);
                destFile.CheckIn("Version History Extractor", SPCheckinType.MajorCheckIn);
            }
            destFile.Update();

            //Redeclare if Declare is checked or was a record and use existing was checked and wasn't already auto-reset to a record
            if ((cbDeclareRecord.Checked || (cbUseExistingRecordStatus.Checked && wasRecord)) && !Records.IsRecord(destFile.Item))
            {
                WriteLog(" ... Declaring Record", false);
                Records.DeclareItemAsRecord(destFile.Item);
            }

            WriteLog(" ... Updated.");
        }

        private string NewFilename(SPFileVersion Version)
        {
            int iLastDotPos = Version.File.Name.LastIndexOf(".");
            string strFilename = Version.File.Name.Substring(0, iLastDotPos) + tbVerDelim.Text;
            //Append either the SharePoint version number or the Version Column (when tbVersionColName isnot null) version number .
            if (string.IsNullOrEmpty(tbVersionColName.Text))
            {
                strFilename += Version.VersionLabel;
            }
            else
            {
                strFilename += Version.Properties[tbVersionColName.Text];
            }
            strFilename += Version.File.Name.Substring(iLastDotPos);
            return strFilename;
        }

        private string NewFilename(SPFile File)
        {
            int iLastDotPos = File.Name.LastIndexOf(".");
            string strFilename = File.Name.Substring(0, iLastDotPos) + tbVerDelim.Text;
            //Append either the SharePoint version number or the Version Column (when tbVersionColName isnot null) version number .
            if (string.IsNullOrEmpty(tbVersionColName.Text))
            {
                strFilename += File.UIVersionLabel;
            }
            else
            {
                strFilename += File.Properties[tbVersionColName.Text];
            }
            strFilename += File.Name.Substring(iLastDotPos);
            return strFilename;
        }

        private System.IO.StreamWriter logFileStream;
        private void InitializeLog()
        {
            logFileStream = new System.IO.StreamWriter(tbLogFilePath.Text); 
        }

        private void FinalizeLog()
        {
            logFileStream.Close();
        }
        
        private void WriteLog(string Message, bool NewLine = true)
        {
            if (NewLine) Message += Environment.NewLine;
            this.tbStatus.Text += Message;
            tbStatus.Refresh();
            this.Refresh();
            tbStatus.Select(tbStatus.Text.Length - 1, 0);
            tbStatus.ScrollToCaret();
            logFileStream.Write(Message);
            logFileStream.Flush();
        }

        private void cbOverwrite_CheckedChanged(object sender, EventArgs e)
        {
            cbUseExistingRecordStatus.Checked = false;
            cbUseExistingRecordStatus.Enabled = cbOverwrite.Checked;
        }

        private void tbLatestColumnName_TextChanged(object sender, EventArgs e)
        {

        }
    }
}
